import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;

public class UDPServer {
	
	private static HashMap<String, ArrayList<PSPEvent>> channel_map;
	private static HashMap<String, String> subscribe_map;
	private static ArrayList<String> packet_list; // Need to clean this up
	
	public static void main(String[] args) throws Exception {
		DatagramSocket serverSocket = new DatagramSocket(9876);
		serverSocket.setSoTimeout(0);		
		boolean stay_connected = true;
		channel_map = new HashMap<String, ArrayList<PSPEvent>>();
		subscribe_map = new HashMap<String, String>();
		int packet_counter = 0;		
		
		while (stay_connected){
			byte[] receiveData = new byte[1024];
			byte[] sendData = new byte[1024];
			// Thread for checking the channel lists and pushing out event notices when necessary
			
			DatagramPacket receivePacket = new DatagramPacket(receiveData, receiveData.length);
			serverSocket.receive(receivePacket);
			
			String sentence = new String(receivePacket.getData());
			if (sentence.trim().equalsIgnoreCase("quit")){
				stay_connected = false;
				sentence = "Connection terminated";
			}
							
			InetAddress IPAddress = receivePacket.getAddress();
			int port = receivePacket.getPort();
			PSPNotify temp_notify = new PSPNotify(channel_map, subscribe_map);
			temp_notify.setConnectionInfo(IPAddress, port);
			
			Thread notifyThread = new Thread(temp_notify, "NotifyThread");
			
			// Handle a packet and determine what is it doing
			sendData = handlePacket(receivePacket);
			
			DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length, IPAddress, port);
			serverSocket.send(sendPacket);
			
			// Wait for receipt from client 
			try{
				// serverSocket.setSoTimeout(2000); // Will be set for final product
				serverSocket.receive(receivePacket);				
			}
			catch(Exception e){
				if (packet_counter < 5){
					packet_counter++;					
				}
				else{
					receivePacket = null;
				}
			}
			packet_counter = 0;
			// Reset the server timeout
			serverSocket.setSoTimeout(0);
			
			if (receivePacket == null){
				System.out.println("Never received response from Client");
				// Do nothing
				continue;
			}
			else
				System.out.println("Response back from Client");
			notifyThread.start();
				
		}
		System.out.println("Client disconnected");
		serverSocket.close();
	}
	
	// Probably change this to message, not boolean
	private static byte[] handlePacket(DatagramPacket receivePacket) {
		/* packet_data breakdown
		 * 0 = PSPAction
		 * 1 = Timestamp
		 * 2 = Client ID
		 * 3 = Packet ID
		 * 4 = Channel Name
		 * 5 = Publication Schedule
		 * 6 = Packet Data
		 */
		
		String[] packet_data = decodePacket(receivePacket);
		byte[] response_data = new byte[Globals.packetSize];
		
		// If the command is to create a channel
		if (PSPAction.valueOf(packet_data[0]) == PSPAction.CLIENT_SET_CHANNEL){
			// If this is a new channel
			if (!channel_map.containsKey(packet_data[4])){
				// Create a new channel with this name
				try{
					channel_map.put(packet_data[4], new ArrayList<PSPEvent>());
					
					// Create a response data packet
					response_data = PSPPacket.create_packet(PSPAction.SERVER_SET_CHANNEL, 
															packet_data[2], 
															Integer.parseInt(packet_data[3].trim()), 
															packet_data[4],
															PSPPubSchedule.valueOf(packet_data[5]),
															"Created:"+packet_data[4]); 
				}catch(Exception e){
					e.printStackTrace();
					System.out.println("Channel could not be created");
					response_data = PSPPacket.create_packet(PSPAction.SERVER_RESPONSE_NEG, 
							packet_data[1], 
							Integer.parseInt(packet_data[2].trim()), 
							packet_data[3],
							PSPPubSchedule.valueOf(packet_data[4]),
							"Failed:"+packet_data[3]); 
					return response_data;
				}
			}
			else if(channel_map.containsKey(packet_data[3])){
				System.out.println("Channel " + packet_data[3] + " already exists.");
				response_data = PSPPacket.create_packet(PSPAction.SERVER_RESPONSE_NEG, 
						packet_data[2], 
						Integer.parseInt(packet_data[3].trim()), 
						packet_data[4],
						PSPPubSchedule.valueOf(packet_data[5]),
						"Duplicate:"+packet_data[4]); 
			}
			else{
				System.out.println("Channel ID failed");
				response_data = PSPPacket.create_packet(PSPAction.SERVER_RESPONSE_NEG, 
						packet_data[2], 
						Integer.parseInt(packet_data[3].trim()), 
						packet_data[4],
						PSPPubSchedule.valueOf(packet_data[5]),
						"Unknown:"+packet_data[4]); 
				return response_data;
			}
		}
		
		// Publish to a channel
		else if (PSPAction.valueOf(packet_data[0]) == PSPAction.CLIENT_PUBLISH_EVENT){
			// If the event was successfully published
			if (publishEvent(packet_data)){
				response_data = PSPPacket.create_packet(PSPAction.SERVER_PUBLISH_EVENT, 
						packet_data[2], 
						Integer.parseInt(packet_data[3].trim()), 
						packet_data[4],
						PSPPubSchedule.valueOf(packet_data[5]),
						"EventAdded:"+packet_data[4]);
			}
			else
				response_data = PSPPacket.create_packet(PSPAction.SERVER_RESPONSE_NEG, 
						packet_data[2], 
						Integer.parseInt(packet_data[3].trim()), 
						packet_data[4],
						PSPPubSchedule.valueOf(packet_data[5]),
						"EventNotAdded:"+packet_data[4]);
		}
		// Subscribe to a channel
		else if (PSPAction.valueOf(packet_data[0]) == PSPAction.CLIENT_SUBSCRIBE_CHANNEL){
			if (subscribeChannel(packet_data)){
				response_data = PSPPacket.create_packet(PSPAction.SERVER_SUBSCRIBE_CHANNEL, 
						packet_data[2], 
						Integer.parseInt(packet_data[3].trim()), 
						packet_data[4],
						PSPPubSchedule.valueOf(packet_data[5]),
						"SubscribeValid:"+packet_data[4]);												
			}
			else{
				response_data = PSPPacket.create_packet(PSPAction.SERVER_RESPONSE_NEG, 
						packet_data[2], 
						Integer.parseInt(packet_data[3].trim()), 
						packet_data[4],
						PSPPubSchedule.valueOf(packet_data[5]),
						"SubsribeFail:"+packet_data[4]);
			}
		}
		
		return response_data;
	}

	private static boolean subscribeChannel(String[] packet_data) {
		String[] channel_arr;
		
		// Split the channel names on semicolons
		try{
			channel_arr = packet_data[4].split(";");
		}catch (Exception e){
			return false;
		}
		
		// Loop through all of the channels provided by the user
		for (int i=0; i<channel_arr.length; i++)
		{
			String channel_name = channel_arr[i];
		// Check to see if the channel exists.
		if (channel_map.containsKey(channel_name)){
			// The channel exists
			// Add the current Client to that channel's subscription list
			try{
				// First check to see if that Channel already exists.
				String temp_sub_list = subscribe_map.get(channel_name);
				// If no record exists, create a list 
				if (temp_sub_list == null)
					temp_sub_list = packet_data[2] + Globals.data_separator;
				// Else, check to see if the user already exists and, if not, add to the list
				else{
					if (!temp_sub_list.contains(packet_data[2] + Globals.data_separator))
						temp_sub_list = temp_sub_list + packet_data[2] + Globals.data_separator;
				}
				subscribe_map.put(channel_name, temp_sub_list);
			}
			// Something bad happened
			catch (Exception e){
				return false;
			}
		}
		else
			return false;
		}
		
		return true;
	}

	private static boolean handleCookie(DatagramPacket receive_packet){
		//Cookie user_cookie = new Cookie("md2835", "ID data"); 
		return true;		
	}

	// Decodes the packet and returns an ArrayList of the sections
	private static String[] decodePacket(DatagramPacket receive_packet){		
		String receive_message = new String(receive_packet.getData()).trim();
		String[] packet_data;
		try{
			 packet_data = receive_message.split(Globals.data_separator);
		}
		catch(Exception e){
			packet_data = null;
		}
		
		return packet_data;
	}
	
	private static boolean publishEvent(String[] packet_data){
		// Check to see if channel exists.
		if (channel_map.containsKey(packet_data[4])){
			try{
			// Create the new event
			PSPEvent m_event = new PSPEvent(packet_data[4], packet_data[6].getBytes());
			// Get the current list of events for this channel
			ArrayList<PSPEvent> temp_arr = channel_map.get(packet_data[4]);
			// Add this event to the channel
			temp_arr.add(m_event);			
			channel_map.put(packet_data[4], temp_arr);
			}
			catch (Exception e){
				return false;
			}
		}
		else{
			return false;
		}
		return true;
	}
	
	private static String [] sendNotifications(){
		return null;
		
	}
}

